Skip to content

Docs/add b20 spec - #1766

Open
soheimam wants to merge 5 commits into
masterfrom
docs/add-b20-spec
Open

Docs/add b20 spec#1766
soheimam wants to merge 5 commits into
masterfrom
docs/add-b20-spec

Conversation

@soheimam

@soheimam soheimam commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

What changed? Why?

  • Updated b20 docs to cover spec and implementation

@mintlify

mintlify Bot commented Aug 3, 2026

Copy link
Copy Markdown

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated (UTC)
base 🟢 Ready View Preview Aug 3, 2026, 2:14 PM

💡 Tip: Enable Workflows to automatically generate PRs for you.

@cb-heimdall

Copy link
Copy Markdown
Collaborator

🟡 Heimdall Review Status

Requirement Status More Info
Reviews 🟡 0/1
Denominator calculation
Show calculation
1 if user is bot 0
1 if user is external 0
2 if repo is sensitive 0
From .codeflow.yml 1
Additional review requirements
Show calculation
Max 0
0
From CODEOWNERS 0
Global minimum 0
Max 1
1
1 if commit is unverified 0
Sum 1

| `TRANSFER_RECEIVER_POLICY` | `to` | `transfer`, `transferFrom`, and memo variants |
| `TRANSFER_EXECUTOR_POLICY` | `msg.sender` | `transferFrom` only when `msg.sender != from` |
| `MINT_RECEIVER_POLICY` | `to` | `mint` and `mintWithMemo` |
| `SEIZE_HOLDER_POLICY` | `from` | `seizeWithMemo`; the holder is seizable only when not authorized by this policy |

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you double check the policies are all listed? Looks like we need to add SEIZE_RECEIVER_POLICY as well.

@roethke roethke left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed against base/base-std as the source of truth. Overall the generated reference is mostly faithful: precompile addresses, roles, variant bytes, decimals, PolicyType values, ~38 IB20 function pages, all IB20Asset pages, and every non-enum selector check out.

A few things worth fixing before merge (inline):

  • Systematic selector/topic bug: enum params are hashed by type name instead of uint8, so every enum-bearing selector/topic is wrong (createB20, getB20Address, B20Created, UnsupportedVersion, createPolicy/createPolicyWithAccounts/createCompositePolicy). Mechanical to regenerate, but breaks integrators.
  • SEIZE_RECEIVER_POLICY missing: the standard has 6 policy scopes; the docs show 5. This also makes seizeWithMemo wrong (it gates the recipient).
  • seizeWithMemo documents a stale signature (returns (bool)) and stale policy semantics.
  • Smaller fixes: finalizeUpdateAdmin access-control, ChildPoliciesOutsideOfRange signature, burnBlocked "no longer part of this interface" (still declared, just deprecated), and missing docs for MIN/MAX_COMPOSITE_CHILD_POLICIES and the UIMultiplierUpdated event.

There's no redirect for the exact old path /base-chain/specs/upgrades/beryl/b20, which will 404 post-merge.


| Field | Value |
|---|---|
| Selector | `0xb263cb84` |

@roethke roethke Aug 6, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Selector is wrong: the enum type name was hashed instead of uint8. ABI selectors normalize enums to uint8, so this should be computed from createB20(uint8,bytes32,bytes,bytes[])0x62975e6a (verified with cast), not createB20(B20Variant,...).

Also wrong: getB20Address (0x8c30260f), the B20Created topic0 (0xfd9bf2730513a1709722ff379a0844dfd8f997d600693c2bcc659e188bbdba0d), UnsupportedVersion (0xc0d8b4e0), and the IPolicyRegistry create* functions. The Canonical signature fields should also show uint8. These would break integrators computing calldata / log filters.


| Field | Value |
|---|---|
| Selector | `0xe494a1f6` |

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same enum-selector bug as createB20: this is computed from createPolicy(address,PolicyType). The correct ABI signature is createPolicy(address,uint8)0xca5d55f6 (verified with cast). Related: createPolicyWithAccounts0xa2d3044f, createCompositePolicy0x6fdd1491. Please regenerate all enum-bearing selectors with uint8 normalization.

## Signature

```solidity
function seizeWithMemo(address from, address to, uint256 amount, bytes32 memo) external returns (bool);

@roethke roethke Aug 6, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two issues vs base-std IB20.sol:474:

  1. Return type: source is ... external; with no return value; the returns (bool) here (and "always true on success" in the description) is incorrect.

function seizeWithMemo(address from, address to, uint256 amount, bytes32 memo) external;

  1. Policy semantics (stale): the page says to is not policy-checked, but source now gates the recipient under SEIZE_RECEIVER_POLICY and reverts PolicyForbids(SEIZE_RECEIVER_POLICY, …) when to isn't authorized (IB20.sol:460–467).

| `TRANSFER_RECEIVER_POLICY` | `to` | `transfer`, `transferFrom`, and memo variants |
| `TRANSFER_EXECUTOR_POLICY` | `msg.sender` | `transferFrom` only when `msg.sender != from` |
| `MINT_RECEIVER_POLICY` | `to` | `mint` and `mintWithMemo` |
| `SEIZE_HOLDER_POLICY` | `from` | `seizeWithMemo`; the holder is seizable only when not authorized by this policy |

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing the 6th policy scope. B20Constants.sol and IB20.sol:274 define SEIZE_RECEIVER_POLICY (gates the seize to recipient), so there are 6 scopes, not 5. Add a row here and update the bytes32[5] array below (~line 75) to include it — as written, the sample audit tool silently skips a real scope. The same omission appears in reference/constants-and-addresses.mdx, index.mdx, token-lifecycle.mdx, and implementation/policy-configuration-in-code.mdx, and there is no dedicated constant page for it.


## Access control

Callable by the policy admin for the target policy.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Access control is inverted. Per IPolicyRegistry.sol:161–168, finalizeUpdateAdmin must be called by the staged pending admin (reverts Unauthorized otherwise) — it "promotes the caller to active admin." The current policy admin cannot call it; that's the point of the two-step transfer.

The child-policy set is capped at 4.
Dev: Reverts with `IncompatiblePolicyType` when `policyType` is not UNION or INTERSECT.
Dev: Reverts with `ZeroAddress` when `admin` is `address(0)`.
Dev: Reverts with `ChildPoliciesOutsideOfRange(2, 4)` when `childPolicyIds.length` is not in `[2, 4]`.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ChildPoliciesOutsideOfRange is parameterless in source (IPolicyRegistry.sol:62: error ChildPoliciesOutsideOfRange();) — writing it as (2, 4) misrepresents the signature. The bound is the named range [MIN_COMPOSITE_CHILD_POLICIES, MAX_COMPOSITE_CHILD_POLICIES]; the literal 2 is not defined in the interface. Same fix needed on updateComposite.

| [`DEFAULT_ADMIN_ROLE`](/base-chain/specs/upgrades/beryl/b20/specification/reference/interfaces/IB20/DEFAULT_ADMIN_ROLE) | `0xa217fddf` | The default top-level admin role (`bytes32(0)`). Required to call `grantRole`, `revokeRole`, |
| [`MINT_ROLE`](/base-chain/specs/upgrades/beryl/b20/specification/reference/interfaces/IB20/MINT_ROLE) | `0xe9a9c850` | Required to call `mint` and `mintWithMemo`. |
| [`BURN_ROLE`](/base-chain/specs/upgrades/beryl/b20/specification/reference/interfaces/IB20/BURN_ROLE) | `0xb930908f` | Required to call `burn` and `burnWithMemo`. |
| [`BURN_BLOCKED_ROLE`](/base-chain/specs/upgrades/beryl/b20/specification/reference/interfaces/IB20/BURN_BLOCKED_ROLE) | `0x32ad9be8` | Required to call the deprecated `burnBlocked` (no longer part of this interface; retained for |

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

burnBlocked is still declared in IB20.sol:453 (marked DEPRECATED), so "no longer part of this interface" is inaccurate. Two related gaps: burnBlocked(address,uint256) has no reference page (the only non-constant IB20 function without one), and SEIZE_RECEIVER_POLICY() (IB20.sol:274) is missing from this function table.

---


## Functions

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two functions declared in IPolicyRegistry.sol are missing from this index (and have no pages): MIN_COMPOSITE_CHILD_POLICIES() (:242) and MAX_COMPOSITE_CHILD_POLICIES() (:247). 16 functions in source, 14 documented — and these two are exactly what the composite-range prose references.

| error | `LengthMismatch` | `0xab8b67c6` | `IB20Asset` | A batched function was called with parallel arrays of differing lengths. |
| event | `Memo` | `0x6989f5818dcfd11f8cd53b27c94cec33dae1589735f03e639cba54553a1825e8` | `IB20` | Emitted by `transferWithMemo`, `transferFromWithMemo`, `mintWithMemo`, and `burnWithMemo` |
| error | `MissingRequiredField` | `0x4a43ae87` | `IB20Factory` | A required string argument was the empty string. |
| event | `MultiplierUpdateCancelled` | `0xf8929975f3e67bbd1e5ec70d4cceaa7cce7ea0e811720f29c96cf1724de09397` | `IB20Asset` | A scheduled multiplier update was cancelled. Emitted by `cancelScheduledMultiplier`, |

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

UIMultiplierUpdated(uint256 oldMultiplier, uint256 newMultiplier, uint256 effectiveAtTimestamp) is missing from this index. It's declared in IScaledUIAmount.sol (inherited by IB20Asset) and is the primary event emitted on multiplier changes — MultiplierUpdateCancelled is listed, but the update event itself is not.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants